﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;

namespace CurveAdjustPlugin
{
    public class CatmullSpline
    {
        List<PointF> ctrlPoints = new List<PointF>();

        public CatmullSpline()
        {
            ClearCtrlPoints();
        }

        public void AddCtrlPoint(PointF point)
        {
            ctrlPoints.Add(point);
            ctrlPoints.Sort(new Comparison<PointF>((PointF p1, PointF p2)=>{return p1.X<p2.X?-1:p1.X>p2.X?1:0;}));
            RecalcEndPoints();
        }
        public void DeleteCtrlPoint(int i)
        {
            ctrlPoints.RemoveAt(i+1);
            RecalcEndPoints();
        }
        public void ClearCtrlPoints()
        {
            ctrlPoints.Clear();
            ctrlPoints.Add(new PointF(-0.1f, -0.1f));
            ctrlPoints.Add(new PointF(0.0f, 0.0f));
            ctrlPoints.Add(new PointF(1.0f, 1.0f));
            ctrlPoints.Add(new PointF(1.1f, 1.1f));
        }
        public PointF GetPoint(int start, float t)
        {
            if (start < 0)
                start = 0;
            PointF p0 = ctrlPoints[start];
            PointF p1 = ctrlPoints[start + 1];
            PointF p2 = ctrlPoints[start + 2];
            PointF p3 = ctrlPoints[start + 3];
            float t2 = t * t;
            float t3 = t2 * t;
            PointF rs = new PointF();
            rs.Y = 0.5f * (2 * p1.Y + 
                            (-p0.Y + p2.Y) * t + 
                            (2 * p0.Y - 5 * p1.Y + 4 * p2.Y - p3.Y) * t2 +
                            (-p0.Y + 3 * p1.Y - 3 * p2.Y + p3.Y) * t3);
            rs.X = 0.5f * (2 * p1.X +
                            (-p0.X + p2.X) * t +
                            (2 * p0.X - 5 * p1.X + 4 * p2.X - p3.X) * t2 +
                            (-p0.X + 3 * p1.X - 3 * p2.X + p3.X) * t3);
            rs.X = Clampf(rs.X, 0.0f, 1.0f);
            rs.Y = Clampf(rs.Y, 0.0f, 1.0f);
            if (rs.X > 0.99)
                rs.Y = ctrlPoints[ctrlPoints.Count-2].Y;
            if (rs.X < 0.01)
                rs.Y = ctrlPoints[1].Y;
            return rs;
        }

        private float Clampf(float p, float p_2, float p_3)
        {
            return Math.Max(p_2, Math.Min(p, p_3));
        }
        public List<PointF> GetSamplePoints(int samplesPerSeg)
        {
            int segs = ctrlPoints.Count() - 3;
            int count = segs * samplesPerSeg;
            List<PointF> points = new List<PointF>();
            points.Add(new PointF(0.0f, ctrlPoints[1].Y));
            for (int i = 0; i < segs; i++)
            {
                for (int j = 0; j < samplesPerSeg; j++)
                {
                    float t = j / (float)samplesPerSeg;
                    points.Add(GetPoint(i, t));
                }
            }
            points.Add(ctrlPoints[ctrlPoints.Count - 2]);
            points.Add(new PointF(1.0f, ctrlPoints[ctrlPoints.Count-2].Y));
            return points;
        }
        public static float GetValue(float x, List<PointF> points)
        {
            int start = points.Count-1;
            for (int i = 0; i < points.Count(); i++)
            {
                if (points[i].X >= x)
                {
                    start = i - 1;
                    break;
                }
            }
            if (start < 0)
                return points[0].Y;
            if (start >= points.Count-1)
                return points[points.Count - 1].Y;
            float t = (x - points[start].X) / (points[start+1].X-points[start].X);
            float value = t * points[start + 1].Y + (1.0f - t) * points[start].Y;
            return Math.Min(1.0f,Math.Max(0.0f,value));
        }
        public int FindNearbyControlPoint(float x, float y, float rad)
        {
            for (int i = 1; i < ctrlPoints.Count() - 1; i++)
            {
                float distX = ctrlPoints[i].X-x;
                float distY = ctrlPoints[i].Y-y;
                float dist = distX*distX + distY*distY;
                if (dist <= rad * rad)
                {
                    return i-1;
                }
            }
            return -1;
        }

        public int GetCtrlPointsCount()
        {
            return ctrlPoints.Count - 2;
        }

        public PointF GetCtrlPoint(int id)
        {
            return ctrlPoints[id + 1];
        }

        const float MinSpacing = 10 / 255.0f;

        public float GetCtrlPointMinX(int id)
        {
            if (id > 0)
                return ctrlPoints[id].X + MinSpacing;
            else
                return 0.0f;
        }

        public float GetCtrlPointMaxX(int id)
        {
            if (id+1 < ctrlPoints.Count - 2)
            {
                return ctrlPoints[id+2].X - MinSpacing;
            }
            else
                return 1.0f;
        }

        public void SetCtrlPoint(int selectedIndex, PointF hitPoint)
        {
            ctrlPoints[selectedIndex+1] = hitPoint;
            RecalcEndPoints();
        }

        private void RecalcEndPoints()
        {
            int c = ctrlPoints.Count - 1;
            PointF d1 = new PointF(ctrlPoints[1].X - ctrlPoints[2].X,
                ctrlPoints[1].Y - ctrlPoints[2].Y);
            ctrlPoints[0] = new PointF(ctrlPoints[1].X + d1.X, ctrlPoints[1].Y + d1.Y);
            PointF d2 = new PointF(ctrlPoints[c-1].X - ctrlPoints[c-2].X,
                ctrlPoints[c-1].Y - ctrlPoints[c-2].Y);
            ctrlPoints[c] = new PointF(ctrlPoints[c-1].X + d2.X, ctrlPoints[c - 1].Y + d2.Y);
        }
    }
}
